#coding:utf8
from helpFunc import *
import socket,select,time,struct
from collections import deque
from base import base
import logging,pickle
from logging.handlers import RotatingFileHandler
import random  
from dam import dam
logger = logging.getLogger('my_logger')
handler = RotatingFileHandler('my_log.log', maxBytes=50*1024, backupCount=3)
formatter = logging.Formatter("%(asctime)s %(message)s",
                              "%Y-%m-%d %H:%M:%S")
handler.setFormatter(formatter)
logger.addHandler(handler)

chiLog = logging.getLogger('chiLog')
handler = RotatingFileHandler('chi.log', maxBytes=700*1024, backupCount=2,encoding='utf8')
handler.setFormatter(formatter)
chiLog.addHandler(handler)

damClass = dam()          
class cli(base):
    def __init__(self,p1,p2,onePakcLen,packEffectTime,timeoutInter,ports,k):
        super().__init__(p1,p2,onePakcLen,packEffectTime,timeoutInter,k)
        self.deadSock = deque(ports)        
        self.aliveSock = deque()
        self.sockMap = {}
        self.staTime = time.monotonic()
        self.staGot = self.staOut = 0
        self.udpCo = 0
        self.lastCheckTimeout = 0
        self.newSockThisSecond = 0
        self.recvTime = 0
        self.getNumPerPack = con_getNum
        self.peerMaxPos = 0    
        self.recvMap = {}
        self.rRaw =self.wRaw = 0
        self.timeoutTime = timeoutTime
        self.peerMaxGotLen = self.peerMaxSendLen = 0
        self.nullSend = 0        
        self.staSend = 0    
        self.newSockLimit = maxNewSockPerSecond
        self.controlSock ,self.msgSock = self.iniControlSock()
        self.staRecv = 0
        self.packNotGot = 0
        self.serverTotalSendLen = serverSenLen
        self.serverOneLen = serverPackLen
        self.totalSend = 0      
        self.chiSQ = deque()
        self.chiRQ = deque()
        self.msgCache = {0:[]}
        self.msgCacheKey = 0
        
    def addMid(self,q,st,maxSize):
        ins = False
        for i in range(len(q)):
            if len(st)>len(q[i])+25:
                q.insert(i,st)
                ins = True
                break
        if len(q)>maxSize:
            q.pop()
        if not ins and len(q)<maxSize:
            q.append(st)     
   
    def cacheSend(self,s):
        if not hasDam:
            return
        self.addMid(self.chiSQ,s,con_damNum)         
        self.msgCache[self.msgCacheKey].append((0,time.time(),s))
    
    def cacheRecv(self,s):
        if not hasDam:
            return        
        self.addMid(self.chiRQ,s,con_damNum)  
        self.msgCache[self.msgCacheKey].append((1,time.time(),s))
                 
    def iniControlSock(self):
        s = socket.socket()
        add = ('127.0.0.1',con_controlPort)
        s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        s.bind(add)
        s.listen(1)
        
        s2 = socket.socket()
        add = ('127.0.0.1',con_controlPort3)
        s2.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        s2.bind(add)
        s2.listen(1)        
        return s,s2
    
    def getId(self):
        self.udpCo+=1
        if self.udpCo==60000:
            self.udpCo=0
        st = b''
        if hasDam:
            st = damClass.damList[self.udpCo%len(damClass.damList)]
        return struct.pack('H',self.udpCo)+st
    
    def dealSta(self,ss):
        l = []
        mi = struct.unpack('Q',ss[:8])[0]
        if mi >self.peerPos:
            le = 0
            for i in range(self.peerPos,mi):
                le += len(self.sendMap[i]['data'])
                del self.sendMap[i]
            self.peerPos = mi
            self.tellAddBuf(le)            
        ma = struct.unpack('Q',ss[8:16])[0]
        d = ss[16:]
        co = -1
        pos = 0
        b = 0
        for i in range(mi,ma+1):
            co += 1
            if co==0:
                b = d[pos]
                pos += 1
                if b&128:
                    l.append(i)
                b <<= 1
            else:
                if b&128:
                    l.append(i)
                b <<= 1
                if co==7:
                    co = -1
        for one in l:
            if one in self.sendMap:
                self.sendMap[one]['peerGot'] = True

    def dealRecvPack(self,fd):
        so = self.sockMap[fd]['pysock']
        id = self.sockMap[fd]['id'] 
        try:
            r,b = so.recvfrom(udpReadLen)
        except:
            self.addToDeadSock(fd)
            return            
        w = structWrapper(r)
        idL = w.readByte()
        if w.readArr(idL)!= id:
            self.addToDeadSock(fd)
            return               
        num = self.sockMap[fd]['num']         
        askList = self.sockMap[fd]['askList'] 
        dataList = self.sockMap[fd]['dataList'] 
        if dataList and dataList[0] in self.sendMap:
            self.sendMap[dataList[0]]['peerGot'] = True   
        self.cacheRecv(r)     
        self.lastRecvTime = time.monotonic()
        self.staGot+=1  
        self.rRaw += len(r)
        if len(r)>self.maxGotLen:
            self.maxGotLen = len(r)                     
        tt = time.monotonic()-self.sockMap[fd]['createTime']
        if tt>self.recvTime:
            self.recvTime = tt
        ttt = w.readQ()
        if ttt>self.peerMaxPos:
            self.peerMaxPos = ttt
        pmg = w.readWord()
        if pmg>self.peerMaxGotLen:
            self.peerMaxGotLen=pmg
        pmg = w.readWord()
        if pmg>self.peerMaxSendLen:
            self.peerMaxSendLen = pmg
        sendNum = w.readByte()
        for i in range(len(askList)):
            nn = askList[i]
            if i<sendNum:
                le = w.readWord()
                st = w.readArr(le)                    
            if nn not in self.recvMap or self.recvMap[nn]['got']:
                continue
            if i<sendNum and st:
                self.recvMap[nn]['got'] = True
                self.recvMap[nn]['data'] = st
                self.checkRecvMap()                
            else:
                self.recvMap[nn]['askTime'] -= 1
                self.packNotGot += 1
        hasSta = w.readByte()
        if hasSta == 1:
            self.staRecv += 1
            le = w.readWord()
            sta = w.readArr(le)
            self.dealSta(sta)
        addPackNum = w.readByte()
        for i in range(addPackNum):
            nn = w.readQ()
            ll = w.readWord()
            st = w.readArr(ll)
            if nn<self.recvPos:
                continue
            self.recvMap[nn] = {'got':True,'askTime':0,'data':st}
        self.checkRecvMap()                         
        del self.sockMap[fd]
        self.aliveSock.append((num,so)) 
        
    def doPeriodWork(self):    
        self.msgCacheKey += 1
        self.msgCache[self.msgCacheKey] = []
        for i in range(self.msgCacheKey-62,self.msgCacheKey-60):
            if i in  self.msgCache:
                del  self.msgCache[i]
        if time.monotonic()-self.lastRecvTime>120:
            import os
            os._exit(0)          
        p1,p2 = int(self.rRaw/1024),int(self.wRaw/1024)
        p3,p4 = int(self.rNet/1024),int(self.wNet/1024)
        tt = int(self.recvTime*100)/100
        self.timeoutTime = self.recvTime+con_timeoutPlus
        if self.recvTime==0 or self.recvTime>timeoutTime:
            self.timeoutTime = timeoutTime
        if self.staOut==0:
            self.gotRate = 1
        else:
            self.gotRate = self.staGot/(self.staGot+self.staOut)  
        self.maxAskTime = self.calAskTime()
        st = '%s %s %s %s || %s %s %s %s || %s %s %s %s || %s %s %s'%(len(self.sockMap),self.staGot,self.staOut,tt,
                                           p1,p2,p3,p4,self.maxSenLen,self.peerMaxGotLen,self.maxGotLen,self.peerMaxSendLen,
            self.nullSend,self.staRecv,self.packNotGot)
        logger.warning(st)
        print (st)
        for one in self.chiSQ:
            if con_keyLen%3:
                one = b'\x00'*(3-con_keyLen%3)+one
            st = damClass.b2chi(one)
            chiLog.warning(' <-- '+st)           
        for one in self.chiRQ:
            st = damClass.b2chi(one)
            chiLog.warning(' --> '+st)     
        self.chiRQ = deque()
        self.chiSQ = deque()
        self.staGot = self.staOut = self.newSockThisSecond= self.recvTime=self.staSend=0
        self.rRaw =self.wRaw = 0
        self.rNet = self.wNet = 0
        self.nullSend = self.packNotGot = self.totalSend = 0
        self.peerMaxGotLen = self.peerMaxSendLen = self.maxGotLen=self.maxSenLen=self.staRecv=0         
        
    def sendPacks(self):
        passTime = time.monotonic()-self.staTime
        if passTime >1:
            passTime = 1
        n = passTime*self.newSockLimit-self.newSockThisSecond
        n = int(n)
        if n<0:
            n=0
        if n>len(self.deadSock):
            n = len(self.deadSock)            
        n = len(self.aliveSock)+n        
        if self.gotRate==0:
            minPackNum = 20000
            maxSend =  20000
        else:            
            minPackNum = passTime*(serverStaPerSecond/self.gotRate)
            maxSend =  passTime*(con_maxPackRecv/self.gotRate)
        maxSend =  int(maxSend-self.totalSend)
        if n>maxSend:
            n = maxSend            
        staSendNum = int(minPackNum-self.staSend)        
        if staSendNum<0:
            staSendNum=0
        if staSendNum>n:
            staSendNum=n
        self.staSend += staSendNum
        askList = self.getAskList(n,staSendNum)
        dataList = self.getDataToSend(n)
        self.totalSend += n
        
        for i in range(n):
            al = askList[i:i+1]
            dl = dataList[i:i+1]
            if staSendNum>0:
                staSendNum -= 1
                hasSta = 1
            else:
                hasSta = 0                
                if not al and not dl:
                    continue
            if not self.aliveSock:
                a = self.deadSock.popleft()
                s = socket.socket(type=socket.SOCK_DGRAM)
                s.setblocking(False)
                self.newSockThisSecond += 1
                self.aliveSock.append((a,s))                
            self.packMsg_send(al,dl,hasSta)
            if not al and not dl:
                self.nullSend += 1     
                
    def dealControl(self):
        try:
            a,b = self.controlSock.accept()
            st = b''
            while len(st)!=2:
                st += a.recv(2)
            self.newSockLimit = struct.unpack('H',st)[0]
            print ('newSockLimit changed',self.newSockLimit)
            a.close()
        except Exception as e:
            print (e)
            
    def dealMsgSock(self):
        try:
            a,b = self.msgSock.accept()
            st = b''
            while len(st)!=2:
                st += a.recv(2)
            k = self.msgCacheKey-1 - struct.unpack('H',st)[0]
            if k in self.msgCache:
                st = pickle.dumps(self.msgCache[k])
                a.sendall(st)
            a.close()
            #del self.msgCache[k]
        except Exception as e:
            print (e)   
            
    def work(self):
        if isWindows:
            rl,wl = self.rig(None)
            rl.append(self.controlSock)
            rl.append(self.msgSock)
            for one in self.sockMap:
                so = self.sockMap[one]['pysock']   
                rl.append(so)
            rl,wl,el = select.select(rl,wl,[],self.calWaitTime()/1000)
            self.dealFd(rl,wl)
            if self.controlSock in rl:
                self.dealControl()
            if self.msgSock in rl:
                self.dealMsgSock()                
            for one in rl:
                if one in self.sockMap:
                    self.dealRecvPack(one)
        else:
            po = select.poll()
            po.register(self.controlSock, select.POLLIN)
            po.register(self.msgSock, select.POLLIN)
            for one in self.sockMap:
                so = self.sockMap[one]['pysock']            
                po.register(so, select.POLLIN)
            self.rig(po)
            es = po.poll(self.calWaitTime())     
            for fd,e in es:   
                self.dealFd(fd, e)          
                if fd == self.controlSock.fileno()  and e&select.POLLIN:  
                    self.dealControl()  
                if fd == self.msgSock.fileno()  and e&select.POLLIN:  
                    self.dealMsgSock()                      
                if fd in self.sockMap  and e&select.POLLIN:  
                    self.dealRecvPack(fd)
        self.reFreshSendMap()
        if time.monotonic()-self.lastCheckTimeout>con_timeoutInternal:
            self.lastCheckTimeout = time.monotonic()
            self.dealTimeout()
        self.sendPacks()                    
        if time.monotonic()-self.staTime>1:
            self.staTime = time.monotonic()
            self.doPeriodWork()     

    def checkRecvMap(self):
        while self.recvPos in self.recvMap and self.recvMap[self.recvPos]['got']:
            self.wrietBuf += self.recvMap[self.recvPos]['data']
            del self.recvMap[self.recvPos]
            self.recvPos += 1        
            
    def packMsg_send(self,askList,dataList,hasSta):
        if askList:
            askList = sorted(askList[0])
        tt = time.monotonic()
        a,so = self.aliveSock.popleft()
        w = structWrapper()
        w.writeArr(self.key)
        id = self.getId()
        w.writeByte(len(id))
        w.writeArr(id)
        w.writeQ(self.recvPos)
        w.writeFloat(self.gotRate)
        if not dataList:
            w.writeByte(0)
        else:
            w.writeByte(1)
            w.writeQ(dataList[0])
            w.writeWord(len(self.sendMap[dataList[0]]['data']))
            w.writeArr(self.sendMap[dataList[0]]['data'])
        w.writeByte(hasSta)
        le = len(askList)
        w.writeByte(le)
        if le!=0:
            w.writeQ(askList[0])
            for i in range(le-1):
                w.writeDWord(askList[i+1]-askList[0])
        if isWindows:
            kk = so
        else:
            kk = so.fileno()
        self.sockMap[kk]={'pysock':so,'num':a,'id':id,'askList':askList,
                                   'dataList':dataList,'createTime':tt}
        add = (serverIp,a)
        try:
            so.sendto(w.data,add)
        except:
            pass
        self.cacheSend(w.data)
        self.wRaw += len(w.data)
        if len(w.data)>self.maxSenLen:
            self.maxSenLen = len(w.data)             
        
    def getAskList(self,n,staPackNum):
        if n == 0:
            return []
        staLen = 17+(self.sendPos-self.peerPos)//8
        if not hasDam:
            sendNum = (self.serverTotalSendLen-20-staLen)//(self.serverOneLen+2)
        else:
            sendNum = (self.serverTotalSendLen-20-dam_maxLen-staLen)//(self.serverOneLen+2)
            
        if sendNum<=0:
            sendNum=0
        l = []
        tempList = []
        thisLoopSendTime = 0
        while True:
            thisLoopSendTime+=1
            if thisLoopSendTime > self.maxAskTime:
                if tempList and len(l)<n:
                    l.append(tempList)
                return l
            for i in range(self.recvPos,self.peerMaxPos):
                if staPackNum>0 and len(tempList)>=sendNum:
                    staPackNum-=1
                    l.append(tempList)
                    tempList = []
                    if len(l)==n:
                        return l                             
                if i not in self.recvMap:
                    self.recvMap[i] = {'got':False,'askTime':0,'data':b''}
                if not self.recvMap[i]['got'] and self.recvMap[i]['askTime']<thisLoopSendTime:
                    if i in tempList:
                        l.append(tempList)
                        tempList = []
                        if len(l)==n:
                            return l                        
                    self.recvMap[i]['askTime']+=1
                    tempList.append(i)
                    if len(tempList)==self.getNumPerPack:
                        l.append(tempList)
                        tempList = []
                        if len(l)==n:
                            return l    
                        
    def getDataToSend(self,n):
        if n == 0:
            return []        
        l = []
        thisLoopSendTime = 0
        while True:
            thisLoopSendTime+=1
            if thisLoopSendTime > self.maxAskTime:
                return l
            for i in range(self.peerPos,self.sendPos):
                if not self.sendMap[i]['peerGot'] and self.sendMap[i]['sendTime']<thisLoopSendTime:
                    self.sendMap[i]['sendTime']+=1
                    l.append(i)
                    if len(l)==n:
                        return l        
        
    def addToDeadSock(self,k):
        so = self.sockMap[k]['pysock']
        num = self.sockMap[k]['num']                   
        askList = self.sockMap[k]['askList']                   
        dataList = self.sockMap[k]['dataList']    
        for i in askList:
            if i in self.recvMap and not self.recvMap[i]['got']:
                self.recvMap[i]['askTime'] -= 1
        if dataList and dataList[0] in self.sendMap and not self.sendMap[dataList[0]]['peerGot']:
            self.sendMap[dataList[0]]['sendTime'] -= 1
                
        del self.sockMap[k]
        self.staOut +=1
        so.close()
        self.deadSock.append(num) 

    def dealTimeout(self):
        tt = time.monotonic()
        for k in list(self.sockMap.keys()):
            if self.sockMap[k]['createTime']+self.timeoutTime<tt:
                self.addToDeadSock(k)
if __name__ == '__main__':
    import threading
    l = findNPorts(2,socket.SOCK_STREAM)
    def ww():
        a = cli(l[0],l[1],cliPackLen,
                 con_packEffectTime,con_timeoutInternal,con_listenPorts,b'tr')
        while True:
            a.work()

    t = threading.Thread(target=ww)
    t.setDaemon(True)
    t.start()
    from testconn import testconn
    xx = testconn(l[0],l[1])


                                     

